/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * language-support-plugin
 * 
 * language-support-plugin is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * language-support-plugin is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <string.h>

#include "node-symbol.h"
#include "ijs-symbol.h"
#include "util.h"

static void node_symbol_interface_init (IJsSymbolIface *iface);
static GList* node_symbol_get_arg_list (IJsSymbol *obj);
static gint node_symbol_get_base_type (IJsSymbol *obj);
static GList* node_symbol_get_func_ret_type (IJsSymbol *obj);
static IJsSymbol* node_symbol_get_member (IJsSymbol *obj, const gchar * name);
static const gchar * node_symbol_get_name (IJsSymbol *obj);
static gboolean node_symbol_is_deleted (IJsSymbol *obj);
static GList* node_symbol_list_member (IJsSymbol *obj);

static gchar* get_complex_node_type (JSParseNode *node, Context *my_cx);
static GList* get_member_list (const gchar *tname, Context *my_cx);
static JSParseNode* get_member (const gchar *tname, const gchar *mname, Context *my_cx);
static GList* get_func_ret_type (const gchar* name, Context *my_cx);


typedef struct _NodeSymbolPrivate NodeSymbolPrivate;
struct _NodeSymbolPrivate
{
	gchar * name;
	JSParseNode *node;
	Context *my_cx;
};

#define NODE_SYMBOL_PRIVATE(o)  (G_TYPE_INSTANCE_GET_PRIVATE ((o), NODE_TYPE_SYMBOL, NodeSymbolPrivate))

G_DEFINE_TYPE_WITH_CODE (NodeSymbol, node_symbol, G_TYPE_OBJECT,
			G_IMPLEMENT_INTERFACE (IJS_TYPE_SYMBOL,
						node_symbol_interface_init));

static void
node_symbol_init (NodeSymbol *object)
{
	NodeSymbolPrivate *priv = NODE_SYMBOL_PRIVATE(object);
	priv->name = NULL;
	priv->node = NULL;
	priv->my_cx = NULL;
}

static void
node_symbol_finalize (GObject *object)
{
	/* TODO: Add deinitalization code here */

	G_OBJECT_CLASS (node_symbol_parent_class)->finalize (object);
}

static void
node_symbol_class_init (NodeSymbolClass *klass)
{
	GObjectClass* object_class = G_OBJECT_CLASS (klass);
	GObjectClass* parent_class = G_OBJECT_CLASS (klass);

	g_type_class_add_private (klass, sizeof (NodeSymbolPrivate));

	object_class->finalize = node_symbol_finalize;
}


NodeSymbol*
node_symbol_new (JSParseNode *node, const gchar *name, Context *my_cx)
{
	NodeSymbol* ret = NODE_SYMBOL (g_object_new (NODE_TYPE_SYMBOL, NULL));
	NodeSymbolPrivate *priv = NODE_SYMBOL_PRIVATE(ret);

	priv->node = node;
	priv->name = g_strdup (name);
	priv->my_cx = my_cx;

	return ret;
}

static void
node_symbol_interface_init (IJsSymbolIface *iface)
{
	iface->get_arg_list = node_symbol_get_arg_list;
	iface->get_base_type = node_symbol_get_base_type;
	iface->get_func_ret_type = node_symbol_get_func_ret_type;

	iface->get_member = node_symbol_get_member;
	iface->get_name = node_symbol_get_name;
	iface->is_deleted = node_symbol_is_deleted;
	iface->list_member = node_symbol_list_member;
}

static GList*
node_symbol_get_arg_list (IJsSymbol *obj)
{
	NodeSymbol* self = NODE_SYMBOL (obj);
	NodeSymbolPrivate *priv = NODE_SYMBOL_PRIVATE(self);

	if ((JSParseNodeArity)priv->node->pn_arity != PN_FUNC)
	{
		g_assert_not_reached ();
		return NULL;
	}

	GList *ret = NULL;

	if (priv->node->pn_u.func.args)
	{
		JSParseNode *i;
		JSParseNode *args = priv->node->pn_u.func.args;

		g_assert (args->pn_arity == PN_LIST);

		for (i = args->pn_u.list.head; i; i = i->pn_next)
		{
			ret = g_list_append (ret, g_strdup (get_node_name (i)));
		}
	}

	return ret;
}

static gint
node_symbol_get_base_type (IJsSymbol *obj)
{
	NodeSymbol* self = NODE_SYMBOL (obj);
	NodeSymbolPrivate *priv = NODE_SYMBOL_PRIVATE(self);

	if ((JSParseNodeArity)priv->node->pn_arity == PN_FUNC)
		return BASE_FUNC;
	return BASE_CLASS;
}

static GList*
node_symbol_get_func_ret_type (IJsSymbol *obj)
{
	NodeSymbol* self = NODE_SYMBOL (obj);
	NodeSymbolPrivate *priv = NODE_SYMBOL_PRIVATE(self);

	if (priv->node->pn_arity != PN_FUNC)
		return NULL;
	return get_func_ret_type (priv->name, priv->my_cx);
}

static IJsSymbol*
node_symbol_get_member (IJsSymbol *obj, const gchar * name)
{
	NodeSymbol* self = NODE_SYMBOL (obj);
	NodeSymbolPrivate *priv = NODE_SYMBOL_PRIVATE(self);

	gchar *tname = get_complex_node_type (priv->node, priv->my_cx);
	if (!tname)
		return NULL;

	if (get_member_list (tname, priv->my_cx))
	{
			return IJS_SYMBOL (
				node_symbol_new (get_member (tname, name, priv->my_cx),
									name, priv->my_cx));
	}
	IJsSymbol *t = global_search (tname);
	if (t)
		return ijs_symbol_get_member (t, name);

	return NULL;
}

static const gchar *
node_symbol_get_name (IJsSymbol *obj)
{
	NodeSymbol* self = NODE_SYMBOL (obj);
	NodeSymbolPrivate *priv = NODE_SYMBOL_PRIVATE(self);

	gchar *tname = get_complex_node_type (priv->node, priv->my_cx);
	if (tname)
		return tname;

	return g_strdup (priv->name);
}

static gboolean
node_symbol_is_deleted (IJsSymbol *obj)
{
	return TRUE;
}

static GList*
node_symbol_list_member (IJsSymbol *obj)
{
	NodeSymbol* self = NODE_SYMBOL (obj);
	NodeSymbolPrivate *priv = NODE_SYMBOL_PRIVATE(self);

	gchar *name = get_complex_node_type (priv->node, priv->my_cx);

	if (!name)
		return NULL;
	GList *t = get_member_list (name, priv->my_cx);
	if (t)
		return t;

	IJsSymbol *sym = global_search (name);
	if (sym)
		return ijs_symbol_list_member (sym);
	return NULL;
}

static gchar*
get_complex_node_type (JSParseNode *node, Context *my_cx)
{
	Type *name = get_node_type (node, my_cx);
	if (!name)
		return NULL;

	if (!name->isFuncCall)
		return name->name;

	IJsSymbol* sym = global_search (name->name);
	if (sym && ijs_symbol_get_base_type (sym) == BASE_FUNC)
	{
		GList *ret = ijs_symbol_get_func_ret_type (sym);
		if (ret)
		{
			g_assert (ret->data != NULL);
			return (gchar*)ret->data;
		}
	}
	return NULL;
}

static GList*
get_member_list (const gchar *tname, Context *my_cx)
{
	GList *i, *ret = NULL;
	gchar *name = g_strconcat (tname, ".prototype", NULL);

//puts (name);
	for (i = g_list_last (my_cx->local_var); i; i = g_list_previous (i))
	{
		Var *t = (Var *)i->data;
		if (!t->name)
			continue;
		if (strncmp (t->name, name, strlen (name)) != 0)
			continue;
//printf ("%s==%s\n", name, t->name);
		if (strlen (name) != strlen (t->name))
			ret = g_list_append (ret, t->name);
		else
		{
			GList *tmp = get_member_from_rc (t->node);
			ret = g_list_concat (ret, tmp);
		}
	}
	for (i = g_list_last (my_cx->childs); i; i = g_list_previous (i))
	{
		Context *t = (Context *)i->data;
		ret = g_list_concat (ret, get_member_list (tname, t));
	}
	return ret;
}

static JSParseNode*
get_member (const gchar *tname, const gchar *mname, Context *my_cx)
{
	GList *i;
	gchar *name = g_strconcat (tname, ".prototype", NULL);
	gchar *full_name = g_strdup_printf ("%s.%s", name, mname);
//puts (name);
	for (i = g_list_last (my_cx->local_var); i; i = g_list_previous (i))
	{
		Var *t = (Var *)i->data;
		if (!t->name)
			continue;
		if (strncmp (t->name, name, strlen (name)) != 0)
			continue;
//printf ("%s==%s\n", name, t->name);
		if (strcmp (t->name, full_name) == 0)
		{
			return t->node;
		}
		else
		{
			JSParseNode *node = get_member_rc (t->node, mname);
			if (node)
				return node;
		}
	}
	for (i = g_list_last (my_cx->childs); i; i = g_list_previous (i))
	{
		Context *t = (Context *)i->data;
		JSParseNode *tmp = get_member (tname, mname, t);
		if (tmp)
			return tmp;
	}
	return NULL;
}

static GList*
get_func_ret_type (const gchar* name, Context *my_cx)
{
	GList *i;
	if (my_cx->func_name)
	{
		if (strcmp (my_cx->func_name, name) == 0)
		{
			return my_cx->ret_type;
		}
	}
	for (i = g_list_last (my_cx->childs); i; i = g_list_previous (i))
	{
		Context *t = (Context *)i->data;
		GList *ret = get_func_ret_type (name, t);
		if (ret)
			return ret;
	}
	return NULL;
}
